/*
 * $Id: tio_get.c,v 1.8 2005-12-26 07:41:48 bacon Exp $
 */

#include <xp/bas/tio.h>
#include <xp/bas/locale.h>
#include <xp/bas/memory.h>
#include <xp/bas/errno.h>

#define STATUS_GETC_EILSEQ  (1 << 0)

xp_ssize_t xp_tio_getc (xp_tio_t* tio, xp_char_t* c)
{
	xp_size_t left = 0;
	xp_ssize_t n;
	xp_char_t curc;
#ifndef XP_CHAR_IS_MCHAR
	xp_size_t seqlen;
#endif

	/* TODO: more efficient to check this...
	 *       maybe better to use xp_assert 
	 * xp_assert (tio->input_func != XP_NULL);
	 */
	if (tio->input_func == XP_NULL) {
		tio->errnum = XP_TIO_ENOINF;
		xp_seterrno (XP_EINVAL);
		return (xp_ssize_t)-1;
	}

	if (tio->input_status & STATUS_GETC_EILSEQ) {
		tio->input_status &= ~STATUS_GETC_EILSEQ;
		tio->errnum = XP_TIO_EILSEQ;
		xp_seterrno (XP_EILSEQ);
		return -1;
	}

	if (tio->inbuf_curp >= tio->inbuf_len) {
getc_conv:
		n = tio->input_func (
			XP_TIO_IO_DATA, tio->input_arg,
			&tio->inbuf[left], xp_countof(tio->inbuf) - left);
		if (n == 0) return 0;
		if (n == -1) {
			tio->errnum = XP_TIO_EINPUT;
			return -1;
		}

		tio->inbuf_curp = 0;
		tio->inbuf_len = (xp_size_t)n + left;	
	}

#ifdef XP_CHAR_IS_MCHAR
	curc = tio->inbuf[tio->inbuf_curp++];
#else
	seqlen = xp_mcseqlen (tio->inbuf[tio->inbuf_curp]);
	if (seqlen == 0) {
		tio->inbuf_curp++;  /* skip one byte */
		tio->errnum = XP_TIO_EILSEQ;
		return -1;
	}

	left = tio->inbuf_len - tio->inbuf_curp;
	if (left < seqlen) {
		xp_memcpy (tio->inbuf, &tio->inbuf[tio->inbuf_curp], left);
		tio->inbuf_curp = 0;
		tio->inbuf_len = left;
		goto getc_conv;
	}
	
	n = xp_mctowc (&tio->inbuf[tio->inbuf_curp], seqlen, &curc);
	if (n == 0) {
		tio->inbuf_curp++; /* skip one byte */
		tio->errnum = XP_TIO_EILSEQ;
		return -1;
	}

	tio->inbuf_curp += n;
#endif

	*c = curc;
	return 1;
}

xp_ssize_t xp_tio_gets (xp_tio_t* tio, xp_char_t* buf, xp_size_t size)
{
	xp_ssize_t n;

	if (size <= 0) return 0;
	n = xp_tio_getsx (tio, buf, size - 1);
	if (n == -1) return -1;
	buf[n] = XP_CHAR('\0');
	return n;
}

xp_ssize_t xp_tio_getsx (xp_tio_t* tio, xp_char_t* buf, xp_size_t size)
{
	xp_ssize_t n;
	xp_char_t* p, * end, c;

	if (size <= 0) return 0;

	p = buf; end = buf + size;
	while (p < end) {
		n = xp_tio_getc (tio, &c);
		if (n == -1) {
			if (p > buf && tio->errnum == XP_TIO_EILSEQ) {
				tio->input_status |= STATUS_GETC_EILSEQ;
				break;
			}
			return -1;
		}
		if (n == 0) break;
		*p++ = c;

		if (c == XP_CHAR('\n')) break;
	}

	return p - buf;
}

xp_ssize_t xp_tio_getstr (xp_tio_t* tio, xp_str_t* buf)
{
	xp_ssize_t n;
	xp_char_t c;

	xp_str_clear (buf);

	for (;;) {
		n = xp_tio_getc (tio, &c);
		if (n == -1) {
			if (buf->size > 0 && tio->errnum == XP_TIO_EILSEQ) {
				tio->input_status |= STATUS_GETC_EILSEQ;
				break;
			}
			return -1;
		}
		if (n == 0) break;

		if (xp_str_ccat(buf, c) == (xp_size_t)-1) {
			tio->errnum = XP_TIO_ENOMEM;
			return -1;
		}

		if (c == XP_CHAR('\n')) break;
	}

	return buf->size;
}
